﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

// Microsoft Dynamics CRM namespaces
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using Microsoft.Crm.Sdk.Query;
using System.Collections.ObjectModel;

namespace AutoNumberPlugin
{

    /// <summary>
    /// Wrapper class for CRM Service
    /// </summary>
    public class CrmServiceWrapper
    {
        #region Members
        private ICrmService mService;
        private CrmService mServiceProxy;
        private IPluginExecutionContext mContext;
        #endregion Members


        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="CrmServiceWrapper"/> class.
        /// Create a new instance of the CRM Service based upon the supplied execution context
        /// </summary>
        /// <param name="context">The context.</param>
        public CrmServiceWrapper(IPluginExecutionContext context)
        {
            if (context == null) throw new ArgumentNullException("context"); //TODO: Replace with CRM safe exception

            mContext = context;

            if (mContext.InvocationSource == MessageInvocationSource.Parent)
                mService = context.CreateCrmService(false);
            else
                mServiceProxy = CrmServiceWrapper.GetCRMProxyUsingEndpointUrlInChildPipeline("http://localhost:5555/mscrmservices/2007/crmservice.asmx", context);
        }
        #endregion Constructors

        #region Private Methods
        /// <summary>
        /// In Child pipeline, you can still use CrmService to connect to CRM but we DONOT Recommend as it is prone to Deadlocks
        /// But if you really want to do it, this is a suggested approach.
        /// </summary>
        /// <param name="endpointUrl">The endpoint URL.</param>
        /// <param name="context">The context.</param>
        /// <returns>CrmService proxy instance.</returns>
        private static CrmService GetCRMProxyUsingEndpointUrlInChildPipeline(string endpointUrl, IPluginExecutionContext context)
        {
            CrmService childCrmService = new CrmService();

            using (childCrmService)
            {
                childCrmService.Url = endpointUrl;
                childCrmService.CrmAuthenticationTokenValue = new CrmAuthenticationToken();
                childCrmService.CrmAuthenticationTokenValue.AuthenticationType = AuthenticationType.AD;
                childCrmService.CrmAuthenticationTokenValue.OrganizationName = context.OrganizationName;
                childCrmService.Credentials = System.Net.CredentialCache.DefaultCredentials;
                childCrmService.CorrelationTokenValue = new CorrelationToken(context.CorrelationId, context.Depth, context.CorrelationUpdatedTime);
            }

            return childCrmService;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Retrieves single dynamic entity given the Guid.
        /// </summary>
        /// <param name="entityName">Name of the entity.</param>
        /// <param name="id">The id.</param>
        /// <param name="columnSet">The required column set.</param>
        /// <returns>Single instance of DynamicEntity or null</returns>
        public DynamicEntity RetrieveDynamicEntityWithGuid(string entityName, Guid id, ColumnSetBase columnSet)
        {
            TargetRetrieveDynamic targetRetrieve = new TargetRetrieveDynamic();

            // Set the properties of the target.
            targetRetrieve.EntityName = entityName;
            targetRetrieve.EntityId = id;

            // Create the request object.
            RetrieveRequest retrieve = new RetrieveRequest();

            // Set the properties of the request object.
            retrieve.Target = targetRetrieve;
            retrieve.ColumnSet = columnSet;

            // Indicate that the BusinessEntity should be retrieved as a DynamicEntity.
            retrieve.ReturnDynamicEntities = true;

            // Execute the request.
            RetrieveResponse retrieved = (RetrieveResponse)Execute(retrieve);

            // Extract the DynamicEntity from the request.
            return (DynamicEntity)retrieved.BusinessEntity;
        }

        /// <summary>
        /// Retrieve the first found Dynamic Entity matching the specified query.
        /// </summary>
        /// <param name="query">Search query.</param>
        /// <returns>Single instance of DynamicEntity or null.</returns>
        public DynamicEntity RetrieveDynamicEntity(QueryBase query)
        {
            // Retrieve the values from CRM as a dynamic entity
            RetrieveMultipleRequest request = new RetrieveMultipleRequest();
            request.ReturnDynamicEntities = true;
            request.Query = query;

            RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)Execute(request);

            // Extract the DynamicEntity from the request.
            if (retrieved.BusinessEntityCollection.BusinessEntities.Count > 0)
            {
                // Return the first entity
                return (DynamicEntity)retrieved.BusinessEntityCollection.BusinessEntities[0];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Retrieves the first found Dynamic Entity given the search attribute name and value.
        /// </summary>
        /// <param name="entityName">Name of the entity.</param>
        /// <param name="searchAttributeName">Name of the primary attribute.</param>
        /// <param name="searchAttributeValue">The primary attribute value.</param>
        /// <param name="columnSet">The required column set.</param>
        /// <returns>Single instance of DynamicEntity or null.</returns>
        public DynamicEntity RetrieveDynamicEntity(string entityName, string searchAttributeName, string searchAttributeValue, ColumnSetBase columnSet)
        {
            // Create a query expression and set the query parameters
            QueryExpression query = new QueryExpression();
            query.EntityName = entityName;
            query.ColumnSet = columnSet;
            query.Criteria.AddCondition(searchAttributeName, ConditionOperator.Equal, searchAttributeValue);

            return RetrieveDynamicEntity(query);
        }

        /// <summary>
        /// Retrieves first found Dynamic Entity matching the given conditions.
        /// </summary>
        /// <param name="entityName">Name of the entity.</param>
        /// <param name="conditionExpressions">The condition expressions.</param>
        /// <param name="columnSet">The required column set.</param>
        /// <returns>Single instance of DynamicEntity or null.</returns>
        public DynamicEntity RetrieveDynamicEntityWithMultipleCondition(string entityName, Collection<ConditionExpression> conditionExpressions, ColumnSetBase columnSet)
        {
            // Create a query expression and set the query parameters
            QueryExpression query = new QueryExpression();
            query.EntityName = entityName;
            query.ColumnSet = columnSet;
            if (conditionExpressions != null && conditionExpressions.Count > 0)
            {
                foreach (ConditionExpression conditionExpression in conditionExpressions)
                {
                    query.Criteria.AddCondition(conditionExpression);
                }
            }

            return RetrieveDynamicEntity(query);
        }

        /// <summary>
        /// Retrieves multiple Dynamic Entities matching the searchAttributeName, searchAttributeValue specified.
        /// </summary>
        /// <param name="entityName">Name of the entity.</param>
        /// <param name="searchAttributeName">Name of the search attribute.</param>
        /// <param name="searchAttributeValue">The search attribute value.</param>
        /// <param name="columnSet">The required column set.</param>
        /// <returns>List of DynamicEntity objects.</returns>
        public Collection<DynamicEntity> RetrieveMultipleDynamicEntity(string entityName, string searchAttributeName, string searchAttributeValue, ColumnSetBase columnSet)
        {
            // Create a query expression and set the query parameters
            QueryExpression query = new QueryExpression();
            query.EntityName = entityName;
            query.ColumnSet = columnSet;
            if (!string.IsNullOrEmpty(searchAttributeName) && !string.IsNullOrEmpty(searchAttributeValue))
                query.Criteria.AddCondition(searchAttributeName, ConditionOperator.Equal, searchAttributeValue);

            return RetrieveMultipleDynamicEntity(query);
        }

        /// <summary>
        /// Retrieves multiple Dynamic Entities matching the specified query.
        /// </summary>
        /// <param name="query">Search query.</param>
        /// <returns>List of DynamicEntity objects.</returns>
        public Collection<DynamicEntity> RetrieveMultipleDynamicEntity(QueryBase query)
        {
            Collection<DynamicEntity> retrievedDynamicEntities = null;

            // Retrieve the values from CRM as a dynamic entity
            RetrieveMultipleRequest request = new RetrieveMultipleRequest();
            request.ReturnDynamicEntities = true;
            request.Query = query;

            RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)Execute(request);

            // Extract the DynamicEntity from the request.
            if (retrieved.BusinessEntityCollection.BusinessEntities.Count > 0)
            {
                retrievedDynamicEntities = new Collection<DynamicEntity>();

                foreach (BusinessEntity businessEntity in retrieved.BusinessEntityCollection.BusinessEntities)
                {
                    retrievedDynamicEntities.Add((DynamicEntity)businessEntity);
                }
            }

            return retrievedDynamicEntities;
        }

        /// <summary>
        /// Retrieves multiple Dynamic Entities matching the given conditions.
        /// </summary>
        /// <param name="entityName">Name of the entity.</param>
        /// <param name="conditionExpressions">The condition expressions.</param>
        /// <param name="columnSet">The required column set.</param>
        /// <returns>List of DynamicEntity objects.</returns>
        public Collection<DynamicEntity> RetrieveMultipleDynamicEntityWithMultipleCondition(string entityName, Collection<ConditionExpression> conditionExpressions, ColumnSetBase columnSet)
        {
            // Create a query expression and set the query parameters
            QueryExpression query = new QueryExpression();
            query.EntityName = entityName;
            query.ColumnSet = columnSet;
            if (conditionExpressions != null && conditionExpressions.Count > 0)
            {
                foreach (ConditionExpression conditionExpression in conditionExpressions)
                {
                    query.Criteria.AddCondition(conditionExpression);
                }
            }

            return RetrieveMultipleDynamicEntity(query);
        }

        /// <summary>
        /// Sets the dynamic entity status (Active and Inactive only).
        /// </summary>
        /// <param name="entityId">The entity id.</param>
        /// <param name="entityName">Name of the entity.</param>
        /// <param name="active">if set to <c>true</c> [active]. otherwise if set to <c>false</c> [inactive].</param>
        public void SetDynamicEntityStatus(Guid entityId, string entityName, bool active)
        {
            string newState = "Active";
            int newStatus = 1;

            if (!active)
            {
                newState = "Inactive";
                newStatus = 2;
            }

            //Create a request object
            SetStateDynamicEntityRequest stateRequest = new SetStateDynamicEntityRequest();
            //Use string and not integer value for state. (For Lead use "Qualified" and not 2)
            stateRequest.State = newState;
            stateRequest.Status = newStatus;

            //Create an instance of the entity
            Moniker m = new Moniker();
            m.Id = entityId;
            m.Name = entityName;

            //Set the state. Well... set the entity.
            stateRequest.Entity = m;

            //Send the request
            Execute(stateRequest);
        }

        /// <summary>
        /// Executes the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns>Response object</returns>
        public object Execute(object request)
        {
            if (mContext.InvocationSource == MessageInvocationSource.Parent)
                return mService.Execute(request);
            else
                return mServiceProxy.Execute((Request)request);
        }

        /// <summary>
        /// Creates the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Create(BusinessEntity entity)
        {
            if (mContext.InvocationSource == MessageInvocationSource.Parent)
                mService.Create(entity);
            else
                mServiceProxy.Create(entity);
        }

        /// <summary>
        /// Updates the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Update(BusinessEntity entity)
        {
            if (mContext.InvocationSource == MessageInvocationSource.Parent)
                mService.Update(entity);
            else
                mServiceProxy.Update(entity);
        }
        #endregion Methods


    }
}
